Computer systems typically store and maintain a large amount of data relating to the computer system and its users. For example, a computer system may maintain names, email addresses, and phone numbers of the user of the computer system. The computer system may also maintain information relating to the various printers connected to the computer system. This information may include printer characteristics (e.g., laser and color) and names of the users who are authorized to use each printer. Several different types of computer programs, known as "clients," may need access to this information. For example, one client may be a computer program that needs to determine which printers can be used to print a document in color. Another client may be a computer program that allows a system administrator to update the information, for example, to reflect information relating to a newly added printer. Computer systems have traditionally stored such information in a variety of locations and formats. For example, information relating to a printer may be stored in a configuration file and information relating to a user may be stored in a database. Thus, it was typically very difficult for programmer who was developing a client to even know where to look for, let alone know how to access, this information.
Some computer systems provide a directory service to help in the storing and maintaining of this information. A directory service provides a repository of this information from which a client can access over the network. Directory services generally refer to the things (e.g., users, printers, or access groups) for which information is maintained as "objects." Directory services organize such objects hierarchically into a directory. That is, one object, referred to as a container object, may contain various other objects referred to a contained objects. For example, an access group that comprises users who share common access rights to a resource is a container object that contains users that are contained objects. Each object for which a directory service maintains information has a unique identifier (e.g., a name) by which a client can identify the object. Although directory services were originally developed to maintain information relating to computer systems, they may be used to maintain information unrelated to computer systems. For example, a directory service can be used by an airplane manufacturer to maintain a part list for the components and sub-components of an airplane.
FIG. 1 illustrates a sample hierarchy of a directory service. Each block represents an object of a particular "object class." For example, an object representing a company would have an object class named "company." Each object class defines the properties of objects of that object class. For example, the company object class may define the properties "name" and "address." Each object has a property value for each property defined for the object class. For example, an object of the company object class may have the property value "MS" for the name property. Block 101 corresponds to an object of the company object class and has a value for the name and address properties. Block 102 corresponds to an object of the division object class and has a value for the name of the division (e.g., "system"). Block 103 corresponds to an object of the user object class and has a value for the email address of the user. Because the directory service is hierarchically organized, each object can be uniquely identified by a path from the root to the object. For example, object 102 is uniquely identified by the path "Company=MS.backslash.Division=Systems". The directory service provides a conceptual finite space, referred to as a namespace, in which a given name can be resolved. The directory service unambiguously resolves paths to objects in the directory.
Various vendors provide directory service systems. Each vendor typically designs and implements an application programming interface (API) to allow clients to access its directory service system. Thus, each directory service system may, and typically does, have very different API sets. If a client needs to use different directory service systems, for example, because the client operates in an environment that includes personal computers and mainframe computers, the programmer of the client would need to know about the API sets of each directory service and design the client to support each of the API sets.
Although each API set is vendor-specific, the API sets generally provide similar functionality. The API sets generally includes functions for accessing property values of the objects and functions for defining new object classes. The functions for manipulating objects are, for example, OpenObject, ReadObject, WriteObject, ListObjects, CloseObject, CreateObject, and DeleteObject. The function OpenObject is passed the identification (e.g., path) to an object and returns a handle that identifies that object. This handle is subsequently used by the client to identify the open object to the directory service. The function OpenObject uses the identification to identify the object and then locates the properties of the identified object. The function ReadObject is passed a handle to an open object and a list of the names of the properties of that object that are to be retrieved. The function ReadObject returns the current property values for those properties. The function WriteObject is passed a handle to an open object and a list of property name and property value pairs. The function WriteObject sets each named property in the open object to the property value of the pair. The function ListObjects is passed a handle to an open object and returns a list containing the identification of each object that is contained within the open object. The function CloseObject is passed a handle to an open object and closes the object so that it can no longer be accessed with that handle. The function CreateObject is passed an object class, creates an object of the object class that is contained within the open object, and returns a handle to the contained object. The function DeleteObject is passed a handle to an open object and removes the open object from its container object.
The functions for defining the object classes are CreateObjectClass, DeleteObjectClass, CreateProperty, DeleteProperty, AddPropertyToObjectClass, DeletePropertyFromObjectClass, and ListProper-tiesOfObjectClass. The function CreateObjectClass is passed the name of a new object class and creates a new object class. Once an object class is created, then a client can create objects of that object class. The function CreateProperty is passed the name of a property and the property type (e.g., integer or string) and creates a property of that property type (e.g., property name of "address" with property type of "string"). The function AddPropertyToObjectClass is passed the name of an object class and the name of a property and adds the named property as a property of the named object class. The function ListPropertiesOfObjectClass is passed the name of an object class and returns a list of the properties that have been added to that object class. The functions DeleteObjectClass, DeleteProperty, and DeletePropertyFromObjectClass perform the behavior suggested by their names.
The present invention is described below using some object-oriented techniques; thus, an overview of well-known object-oriented programming techniques is provided. (The term "object" has many different meanings when used in different contexts. In the remainder of the background, the term "object" is used in an object-oriented sense to refer to an in-memory data structure.) Two common characteristics of object-oriented programming languages are support for data encapsulation and data type inheritance. Data encapsulation refers to the binding of functions and data. Inheritance refers to the ability to declare a data type in terms of other data types. In the C++ language, data encapsulation and inheritance are supported through the use of classes. A class is a user-defined type. A class declaration describes the data members and function members of the class. A function member is also referred to as a method of a class. The data members and function members of a class are bound together in that the function operates on an instance of the class. An instance of a class is also called an object of the class. Thus, a class provides a definition for a group of objects with similar properties and common behavior.
To allocate storage for an object of a particular type (class), an object is instantiated. Once instantiated, data can be assigned to the data members of the particular object. Also, once instantiated, the function members of the particular object can be invoked to access and manipulate the data members. Thus, in this manner, the function members implement the behavior of the object, and the object provides a structure for encapsulating data and behavior into a single entity.
To support the concept of inheritance, classes may be derived from (based upon the declaration of) other classes. A derived class is a class that inherits the characteristics--data members and function members--of its base classes. A class that inherits the characteristics of another class is a derived class. A class that does not inherit the characteristics of another class is a primary (root) class. A class whose characteristics are inherited by another class is a base class. A derived class may inherit the characteristics of several classes; that is, a derived class may have several base classes. This is referred to as multiple inheritance.
A class may also specify whether its function members are virtual. Declaring that a function member is virtual means that the function can be overridden by a function of the same name and type in a derived class. If a virtual function is declared without providing an implementation, then it is referred to as a pure virtual function. A pure virtual function is a virtual function declared with the pure specifier, "=0". If a class specifies a pure virtual function, then any derived class needs to specify an implementation for that function member before that function member may be invoked. A class which contains at least one pure virtual function member is an abstract class.
FIG. 2 is a block diagram illustrating typical data structures used to represent an object. An object comprises instance data (data members) and function members, which implement the behavior of the object. The data structures used to represent an object comprise instance data structure 201, virtual function table 202, and the function members 203, 204, 205. The instance data structure 201 contains a pointer to the virtual function table 202 and contains data members. The virtual function table 202 contains an entry for each virtual function member defined for the object. Each entry contains a reference to the code that implements the corresponding function member. The layout of this sample object conforms to models described in U.S. Pat. No. 5,297,284, entitled "A Method for Implementing Virtual Functions and Virtual Bases in a Compiler for an Object Oriented Programming Language," which is hereby incorporated by reference. In the following, an object will be described as an instance of a class as defined by the C++ programming language. One skilled in the art would appreciate that other object models can be defined using other programming languages.
An advantage of using object-oriented techniques is that these techniques can be used to facilitate the sharing of objects. For example, a program implementing the function members of an instantiated object (a "server program") can share the object with another program (a "client program"). To allow an object of an arbitrary class to be shared with a client program, interfaces are defined through which an object can be accessed without the need for the client program to have access to the class definitions at compile time. An interface is a named set of logically related function members. In C++, an interface is an abstract class with no data members and whose virtual functions are all pure. Thus, an interface provides a published protocol for two programs to communicate. Interfaces are typically used for derivation: a program defines (implements) classes that provide implementations for the interfaces the classes are derived from. Thereafter, objects are created as instances of these derived classes. Objects instantiated from a derived class implementing particular interfaces are said to "support" the interfaces. An object supports one or more interfaces depending upon the desired functionality.
When a client program desires to share an object, the client program needs access to the code that implements the interfaces for the object (the derived class code). In the OLE 2.01 environment established by Microsoft.TM. Corporation of Redmond, Washington, to access the derived class code (also referred to as class code), each class implementation is given a unique class identifier (a "CLSID"). OLE 2.01 is described in "Inside OLE," 2.sup.nd Edition, Microsoft Press, 1995, by Kraig Brockschmidt. For example, code implementing a spreadsheet object developed by Microsoft Corporation may have a class identifier of "MSSpreadsheet," while code implementing a spreadsheet object developed by another corporation may have a class identifier of "LTSSpreadsheet." A persistent registry in each computer system is maintained that maps each CLSID to the code that implements the class. Typically, when a spreadsheet program is installed on a computer system, the persistent registry is updated to reflect the availability of that class of spreadsheet objects. So long as a spreadsheet developer implements each function member defined by the interfaces to be supported by spreadsheet objects and so long as the persistent registry is maintained, the client program can access the function members of shared spreadsheet objects without regard to which server program has implemented them or how they have been implemented.
Since an object may support some interfaces and not others, a client program may need to determine at run time whether a particular object supports a particular interface. To enable this determination, every COM object supports the IUnknown interface, which contains a function member, QueiryInterface, that indicates which interfaces are implemented for the object. The QueryInterface method is defined as follows:
virtual HRESULT QueryInterface (REFIID iid. void**ppv)=0;
The QueryInterface method is passed an interface identifier in parameter iid (of type REFIID) and returns in parameter ppv a pointer to the implementation of the designated interface "iid." Thus, the QueryInterface method is typically coded to know about all available interfaces of the object to which it belongs. If the object does not support the interface, then the QueryInterface method returns false. The type HRESULT indicates a predefined status.
The IUnknown interface also defines the methods AddRef and Release, which are used to implement reference counting. Whenever a new reference to an interface is created, the AddRef method is invoked to increment a reference count of the object. When a reference is no longer needed, the Release method is invoked to decrement the reference count of the object and, when the reference count goes to zero, the object is deallocated.